import * as vscode from 'vscode'
import * as fs from 'fs'
import { GitChangedFile, GitFilesFromCommitsOptions } from '../git/Git'
import {
  decodeDiffDocUri,
  DiffDocProvider,
  DiffDocUriData,
  encodeDiffDocUri,
} from './diffDocProvider'
import { getLogger } from '../init'
import { BlameLineData, InlineGit } from './InlineGit'
import { timeout } from '../util/timeout'
import { debounce } from '../util/debounce'

export class SidebarWebivewProvider implements vscode.WebviewViewProvider {
  private _getGitRoot: Promise<vscode.Uri>
  public getGitRoot() {
    return this._getGitRoot
  }

  public readonly git: InlineGit

  constructor(private readonly extensionUri: vscode.Uri, private readonly rootUri: vscode.Uri) {
    this.git = new InlineGit(this.rootUri.fsPath)
    this._getGitRoot = this.git.getGitRoot().then((v) => vscode.Uri.file(v.trim()))
  }

  private view?: vscode.WebviewView

  private _cacheBlameFileMap: WeakMap<vscode.TextDocument, BlameLineData> = new WeakMap()
  private _cacheBlameFilePathMap: Map<string, Promise<BlameLineData>> = new Map()

  private postMessage(data: { id?: number; type?: string; data?: any; error?: any }) {
    this.view!.webview.postMessage(data)
  }

  public queryAndPostActiveFilePath(editors?: readonly vscode.TextEditor[]) {
    const editor = this.getCurrentActiveEditor(editors)
    let filePath = ''
    if (editor) {
      const request = decodeDiffDocUri(editor.document.uri)
      filePath = request.filePath
      this.resetSetEditorDecoration(editor, request)
    }
    this.postMessage({
      type: 'changed.activeFilePath',
      data: { filePath },
    })
  }

  private async queryBranches() {
    return await this.git.getBranchList()
  }

  public async queryAndPostBranches() {
    const data = await this.queryBranches()
    return this.postMessage({ type: 'changed.branches', data })
  }

  public async clearAll() {
    this.postMessage({ type: 'action.clearAll', data: null })
  }

  private async queryFiles(opts: GitFilesFromCommitsOptions) {
    const files = await this.git.getDiffFiles(opts)
    return files
  }

  private async blameFile(
    opts: GitChangedFile &
      GitFilesFromCommitsOptions & { preview: boolean } & { selectCommitHashList: string[] }
  ) {
    const isAdd = opts.actionFlag === 'A'
    const isDel = opts.actionFlag === 'D'

    const res = vscode.commands.executeCommand(
      'vscode.diff',
      encodeDiffDocUri({
        filePath: opts.srcFilePath || opts.filePath,
        commit: opts.commitIdStart,
        isNoExist: isAdd ? 'Y' : 'N',
      }),
      encodeDiffDocUri({
        filePath: opts.filePath,
        commit: opts.commitIdEnd,
        isNoExist: isDel ? 'Y' : 'N',
      }),
      opts.filePath,
      { preview: opts.preview ?? true }
    )

    if (!isDel) {
      if (!this._cacheBlameFilePathMap.has(opts.filePath)) {
        const gitRoot = await this.getGitRoot()
        const filePath = vscode.Uri.joinPath(gitRoot, opts.filePath).fsPath
        this._cacheBlameFilePathMap.set(
          opts.filePath,
          this.git.getBlameInDiffAndCommits(
            {
              filePath,
              commitIdStart: opts.commitIdStart,
              commitIdEnd: opts.commitIdEnd,
            },
            opts.selectCommitHashList?.length > 0
              ? new Set(opts.selectCommitHashList)
              : await this.git.getCommitHashs(opts)
          )
        )
      }
    }

    await res
  }

  public getCurrentActiveEditor(
    editors: readonly vscode.TextEditor[] = vscode.window.visibleTextEditors
  ) {
    const l = editors.length
    if (l >= 2) {
      for (let i = l - 1; i >= 0; --i) {
        const editor = editors[i]
        const { uri } = editor.document
        if (typeof uri === 'object' && uri.scheme === DiffDocProvider.scheme) {
          return editor
        }
      }
    }
    return null
  }

  public async resetSetEditorDecoration(activeEditor: vscode.TextEditor, request: DiffDocUriData) {
    const { filePath } = request
    const _pro = this._cacheBlameFilePathMap.get(filePath)
    let args: BlameLineData | undefined
    if (_pro) {
      const event = vscode.window.onDidChangeActiveTextEditor(() => {
        event.dispose()
        this._cacheBlameFilePathMap.delete(filePath)
      })
      args = await _pro
      this._cacheBlameFileMap.set(activeEditor.document, args)
    } else {
      args = this._cacheBlameFileMap.get(activeEditor.document)
    }
    if (args) {
      this.setEditorDecoration(activeEditor, args)
    }
  }

  private async setEditorDecoration(activeEditor: vscode.TextEditor, blameLineData: BlameLineData) {
    const inCommitsDecorationType = {
      overviewRulerColor: 'rgb(255 177 0 / 30%)',
      backgroundColor: 'rgba(0,0,0,0.3)',
      light: {
        after: {
          color: 'rgb(214 168 0 / 57%)',
        },
        backgroundColor: 'rgba(255,255,255,0.3)',
      },
    }
    const notInCommitsDecorationType = {
      overviewRulerColor: 'rgb(0 220 255 / 30%)',
      backgroundColor: 'rgba(0,0,0,0.3)',
      light: {
        after: {
          color: 'rgb(0 149 255 / 42%)',
        },
        backgroundColor: 'rgba(255,255,255,0.3)',
      },
    }
    let invalid = false
    const event = vscode.window.onDidChangeActiveTextEditor((editor) => {
      if (activeEditor !== editor) invalid = true
    })
    let i = 0
    for (const v of blameLineData) {
      if (invalid) break

      const decorationtype = {
        ...(v.inCommits ? inCommitsDecorationType : notInCommitsDecorationType),
        isWholeLine: true,
        overviewRulerLane: vscode.OverviewRulerLane.Full,
      }
      const time = new Date(v.blame.time).toLocaleString()
      activeEditor.setDecorations(
        vscode.window.createTextEditorDecorationType({
          ...decorationtype,
          after: {
            contentText: `${v.blame.author} ${time} ${v.blame.summary}`,
            color: decorationtype.overviewRulerColor,
            margin: '0 0 0 60px',
          },
        }),
        [new vscode.Range(v.startLine - 1, 0, v.startLine - 1, 0)]
      )
      if (v.stepLine > 1) {
        activeEditor.setDecorations(vscode.window.createTextEditorDecorationType(decorationtype), [
          new vscode.Range(v.startLine, 0, v.startLine + v.stepLine - 2, 0),
        ])
      }
      await timeout(Math.min(i++ / 4, 100))
    }
    event.dispose()
  }

  public resolveWebviewView(webviewView: vscode.WebviewView) {
    this.view = webviewView
    const { webview } = webviewView
    webview.options = {
      enableScripts: true,
      localResourceRoots: [this.extensionUri],
    }
    webview.onDidReceiveMessage(async (e: { id?: number; method: string; params: any }) => {
      switch (e.method) {
        case 'queryFiles': {
          const data = await this.queryFiles(e.params)
          return this.postMessage({ id: e.id, type: 'data.files', data })
        }
        case 'blameFile': {
          const data = await this.blameFile(e.params)
          return this.postMessage({ id: e.id, type: 'data.blameFile', data })
        }
        case 'init': {
          await Promise.all([this.queryAndPostBranches(), this.queryAndPostActiveFilePath()])
          return this.postMessage({ id: e.id })
        }
      }
    })

    const htmlPath = vscode.Uri.joinPath(this.extensionUri, 'assets/sidebar/index.html').fsPath
    fs.readFile(htmlPath, 'utf-8', (err, content) => {
      if (err) {
        vscode.window.showErrorMessage(err.message)
        return
      }
      const htmlDir = webview.asWebviewUri(this.extensionUri)
      webview.html = content.replace('<base href="/" />', `<base href="${htmlDir}/" />`)
    })
  }
}
